package af.web.fileupload;

/*源码：demo1601
 * 实现了最原始的浏览器上传文件（图片）到后台，后台接收并处理的过程，
 * 后台把拿到的文件读取出来，改个名字，放到WevRoot-upload下面，再从新取个名字
 * 
 * 由于文件的处理是很复杂的，所以要先导入两个jar包的支持，这两个包在源码demo1601里面有，资料源码跟项目源码都有
 * 1、commons-fileupload-1.3.1.jar
 * 2、commons-io-2.4.jar
 * 这两个jar包是以我们在做网站的时候经常用到的，能简化我们处理文件的过程
 * 
 * 我们为什么要把上传的文件放在WebRoot-upload目录下呢，因为这个目录下的文件可以
 * 直接被浏览器访问并显示，
 * 
 * 在这个演示里面，我们改写了一点点的代码，取得文件的路径还有更改的名字返回给了
 * 浏览器，使得浏览器可以直接访问并显示这个图片
 * 
 * 但是也不一定非要放在这个目录下，我们以后在学，现在就这样
 * 
 * 
 * */

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.json.JSONObject;

@WebServlet("/abc123")
public class AfFileUploadService extends HttpServlet	//一个Servlet
{
	// 文件上传的临时目录 
	File tmpDir ;	// webroot/upload/
	
	@Override
	public void init() throws ServletException		//Servlet打开之前执行的,创建一个文件夹
	{
		File webroot = new File(getServletContext().getRealPath("/"));
		tmpDir = new File(webroot, "upload");
		tmpDir.mkdirs(); // 上传进来的文件存放在 WebRoot/upload/ 下
	}

	protected void doPost(HttpServletRequest request, 		//前端是POST方式传入的，所以在这里处理
			HttpServletResponse response) throws ServletException, IOException
	{
		JSONObject jresp = new JSONObject();
		try
		{
			Object data = doUpload(request, response);	//写一个方法来处理，request是前台传来的，response是传给前台的
			jresp.put("error", 0);
			jresp.put("reason",  "OK");
			if(data != null)
				jresp.put("data", data);
			
		} catch (Exception e)
		{
			jresp.put("error", -1);
			jresp.put("reason", e.getMessage());
		}
		
		// 设置返回的内容类型Content-Type 
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/plain");			
		PrintWriter out = response.getWriter();	//返回给浏览器
		out.write(jresp.toString(2));			//返回给浏览器的数据
		out.close();
	}
	
	private Object doUpload(HttpServletRequest request, 	//处理文件
			HttpServletResponse response) throws Exception
	{		
		//创建一个工具类来处理图片，这个工具类来自commons，就是我们导入的jar包里面的，使用它来解析请求
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		if(!isMultipart)
			throw new Exception("请求编码必须为: multipart/form-data !");	//前端设施的一个请求编码
		
		request.setCharacterEncoding("UTF-8");	//设置编码
			
		// ServletFileUpload ： commons包里提供的工具类
		ServletFileUpload upload = new ServletFileUpload();	//服务器文件上传
		AfFileUploadInfo info = new AfFileUploadInfo();		//这个类是一个用来保存文件信息的类，文件名，大小等等
		
		FileItemIterator iter = upload.getItemIterator(request);	//从请求中获取所有文件
		
		//把请求用while遍历，为什么要遍历呢，因为在表单(前端的form)里面是可以插入多个文件的，所以要
		//循环把每一个都给遍历出来
		while (iter.hasNext()) 	//这里只有一个，iter.hasNext()的长度代表文件个数
		{
			// 表单域 
		    FileItemStream item = iter.next();	//取得第一个文件
		    String fieldName = item.getFieldName();		// fieldName = "file" ;
		    InputStream fieldStream = item.openStream();	// 输入流 = 文件.开发流
		    if (item.isFormField())	//文件是否开启表单域（上传图片：false）
		    {
		    	// 普通表单域: 直接读取值
		    	// 指的是<input>,<select>,<textarea>输入控件的值, 字符串类型
		    	String fieldValue = Streams.asString(fieldStream, "UTF-8");
		    	printLog("表单域:" + fieldName + "=" + fieldValue);			    	
		    } 
		    else 
		    {
		    	//对每一个文件进行处理
		    	// 文件域, 指<file>控件的值，指文件的数据
		    	// 生成唯一的文件名
		    	info.realName = item.getName(); // 获取原始文件名
		    	info.suffix = fileSuffix(info.realName); // 获取文件后缀，传入：文件名
		    	info.tmpFileName = createTmpFileName(info.suffix); // 临时文件名，传入后缀名，就是从新起个不重复的名字
		    	info.tmpFile = new File(tmpDir, info.tmpFileName);	//在webroot/upload/目录下面创建20200318-003850-B8C4A9EFDE16401AA63B86C325F4CDA1.jpg
		    	info.fileSize = 0; // 文件大小			    	
		    	
		    	printLog("文件上传开始:" + info.realName + " >> " + info.tmpFile);	//realName:文件名字 tmpFile:文件内容
			        
		        // 从FieldStream读取数据, 保存到目标文件			        
		        info.tmpFile.getParentFile().mkdirs();		//返回此抽象路径名的父目录的抽象路径名，如果不存在则创建一堆，（就是检查这个目录是否存在，不存在就创建一个）       
		        FileOutputStream fileStream = new FileOutputStream(info.tmpFile);	//文件输出流：传入：File目录
		        try{
		        	// 从请求里读取文件数据，保存到本地文件
		        	info.fileSize = copy(fieldStream, fileStream);	//传入：输入流（源文件） ，文件输出流（接收文件）
		        }finally{
		        	try{ fileStream.close();}catch(Exception e){}
		        	try{ fieldStream.close();}catch(Exception e){}
		        }
		        
		        printLog("文件上传完成:" + info.realName + ", 大小: " + info.fileSize);
		    }
		}	
		
		/*****与上面相比，我们在这里加了一小段，用来取得图片保存的路径，返回给浏览器用来加载显示*/
		// storePath: /upload/20200313-180331-666FA3A5D5424A12B7EC41EC09DC4558.jpg
		String storePath = "/upload/" + info.tmpFileName;		//用文件夹+文件名拼凑出文件名路径
		
		// contextPath: /demo1601
		String url = getServletContext().getContextPath() + storePath;	//把映射路径+上面的路径拼凑成一个路径
		
		//把这个路径转换成一个JSON传给浏览器
		JSONObject result = new JSONObject();	//把路径跟url都返回给浏览器
		result.put("storePath", storePath);
		result.put("url", url);
		
		return result;
		/**********************************************************************************/
	}
	
	private long copy(InputStream in, OutputStream out) throws Exception	//传入：输入流（源文件） ，输出流（接收文件）
	{
		long count = 0;
		byte[] buf = new byte[8192];
		while (true)
		{
			// 1、n=3921 | 2、n=4054 | 3、n=42 | 4、n=4054 | 5、n=4054 | 6、n=84 | 7、n=4054 | 8、n=4054 | 9、n=84 | 10、n=4054
			// 11、n=4054 | 12、n=84 | 13、n=4054 | 14、n=4054 | 15、n=84 | 16、n=4054 | 17、n=4054 | 18、n=84 | 19、n=4054
			// 20、n=4054 | 21、n=84 | 22、n=4054 | 23、n=4054 | 24、n=84 | 25、n=4054 | 26、n=4054 | 27、n=84 | 28、n=4054
			// 29、n=4054 | 30、n=84 | 31、n=4054 | 32、n=4054 | 33、n=84 | 34、n=4054 | 35、n=4054 | 36、n=84 | 37、n=4054
			// 38、n=4054 | 39、n=84 .....
			int n = in.read(buf);	//从输入流中读取一定数量的字节，并将它们存储到缓冲区数组buf中。
			if (n < 0)
				break;				//上面写入好多，下面读取好多，都是n
			if (n == 0)
				continue;
			out.write(buf, 0, n);	//从从0开始的指定字节数组写入n字节到此输出流。

			count += n;
		}
		return count;
	}
	
	//////////////////////////////////////////////////
	
	private void printLog(String message)
	{
		System.out.println(message);
	}
	
	// 生成一个唯一的ID
	private String createUUID ()
	{
		 //用于检索类型4(伪随机生成的)UUID的静态工厂，就是随机生成一段字符，
		 String s = UUID.randomUUID().toString(); 	// s：b8c4a9ef-de16-401a-a63b-86c325f4cda1
		 //把横线去掉
	     String s2 = s.substring(0,8)+s.substring(9,13)+s.substring(14,18)+s.substring(19,23)+s.substring(24); //s2 : b8c4a9efde16401aa63b86c325f4cda1
	     //把小写换成大写
	     return s2.toUpperCase();	//使用默认区域设置的规则将此字符串中的所有字符转换为大写。
	}
	
	// 得到一个保证不重复的临时文件名，用一种规则来拼凑
	private String createTmpFileName(String suffix)
	{
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
		String datestr = sdf.format(new Date());	//格式化当前时间：20200318-003850
		String name = datestr + "-" + createUUID() + "." + suffix;	//拼凑文件名：
		return name;	// 20200318-003850-B8C4A9EFDE16401AA63B86C325F4CDA1.jpg
	}
	
	// 得到文件的后缀名
	public String fileSuffix(String fileName)	// fileName: QQ图片20200316222321.jpg
	{
		int p = fileName.lastIndexOf('.');	// p = 18 ,也就是.之前有多少字数
		if(p >= 0)
		{
			// substring:返回一个字符串，该字符串是此字符串的子字符串。 | toLowerCase:转换成小写
			return fileName.substring(p+1).toLowerCase();	// 返回“QQ图片20200316222321.jpg”的第19位后面的数，就是后缀名
		}
		return "";
	}
}
